package fortunata.fswapps.vpoet;

import com.hp.hpl.jena.ontology.*;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;
import com.hp.hpl.jena.util.FileManager;
import com.hp.hpl.jena.rdf.model.*;

import java.util.ArrayList;

/**
 * This class manages the procedure of getting the value of a property. Also can check if a property has some value
 */
public class PropertyValueGetter {
    private String propName;
    private String[] prefPropNames;
    static final String strValueNA = "N.A.";
    static final String valuesSeparator = "<BR>";

    /**
     * This is used when the property is not a relation. For example: foaf.firstName
     * @param propName
     */
    public PropertyValueGetter(String propName){
        this.propName = propName;
        prefPropNames = null;
    }

    /**
     * This is uses when the property is a relation. For example foaf.knows
     * Relations link two indivuduals, let's say inv1--knows-->indv2
     * @param propName the relation's name. For example foaf.knows
     * @param preferentialPropNames   the list of property names of indv2, sorted by preference.  
     */
    public PropertyValueGetter(String propName, String[]preferentialPropNames){
        this.propName = propName;
        prefPropNames = preferentialPropNames;
    }

    public boolean hasPropertyValue(OntModel m, Individual defaultClassInstance) throws Exception{
         ArrayList values = getPropertyValues(m, defaultClassInstance);
         return (values.size() == 1 && values.get(0).equals(strValueNA))? false: true;
    }
    /**
     * If the property has a value convertible to string, this will be returned. Otherwise, as is the case for
     * properties that are relations, the value of the preferred property of the relation is returned by means
     * of the method showPropertyValue(OntModel m, Individual indv, String[] preferredPropName)
     * @param m
     * @param defaultClassInstance This can be null
     * @return The list of values (String)
     * @throws Exception in case of error. The exception has the right message.
     */
     public ArrayList getPropertyValues(OntModel m, Individual defaultClassInstance) throws Exception {
       Individual indv1 = null;
       OntProperty op = getOntProperty(m); //Further improve: create a cache ...TODO
       if (op == null){
          throw new Exception("Property '" + propName + "' not found");
       }
       if (defaultClassInstance == null){
           try {
              indv1 = getIndividualWithOntProperty(m, op);
           }catch(Exception e){
               throw e;
           }
       }else{
           indv1 = defaultClassInstance;
       }
       //Now we have an individual with the desired property, but in relations...  indv1 --relation--> indv2
       //...the value of the property itself can be another Individual, even a blank node.
        NodeIterator nodes = indv1.listPropertyValues(op);
        RDFNode node = null;
        ArrayList values = new ArrayList();
        try {
            if (nodes.hasNext()){
                while (nodes.hasNext()){
                   node = (RDFNode)nodes.next();
                   values.add(new String(getNodeValue(node, m)));
                }
            }else{
                values.add(new String(strValueNA));
            }
        }catch(Exception e){
            throw e;
        }
        return values;
    }

    private String getNodeValue(RDFNode value, OntModel m) throws Exception {
        Individual indv2;
        String strValue = strValueNA;
        if (value == null){
            return strValueNA;
        }
        //The value can be a literal... take care: URIs are not Literals but Resources
        if (value.isLiteral() || value.isURIResource()){
          strValue = value.toString();
        }else{
            try{
              //Or it can be a Resource
              if (value.isResource()){
                  //This Resource is an Individual... always?
                  if (value.canAs(Individual.class)){
                       indv2 = (Individual)value.as(Individual.class);
                       //strValue = getPreferredString(indv2, m);
                       strValue = getRelationValue(m, indv2);
                  }else{
                       throw new Exception("What?. This node's value is a resource and is not an Individual: " + value.toString());
                  }
              }else{
                  throw new Exception("What?. This node's value is not an resource and is not a Literal/URI: " + value.toString());
              }
            }catch(Exception e){
                throw e;
            }
        }
        return strValue;
    }

    /**
     *
     * @param m
     * @param indv
     * @return
     */
     private String getRelationValue(OntModel m, Individual indv){
       String strValue = null;
       OntProperty op = null;
       RDFNode value;
       if (prefPropNames != null){
           for (int i = 0; i < prefPropNames.length; i++){
               op = getOntPropertyWithPropertyName(m, prefPropNames[i]);
               if (op != null){ //The preferred property exists in the model
                   value = indv.getPropertyValue(op);
                   if (value == null){ //but this individual doesn't have this property... (see  Daniel Krech FOAF: http%3A%2F%2Feikeon.com%2Ffoaf.rdf%23eikeon)
                       //strValue = "N.A. (doesn't have the preferred prop)... but it can have other....";
                   }else{
                       //If the preferred property has a value... and that value is a simple string
                       if (value.isLiteral() || value.isURIResource()){
                           strValue = value.toString();
                           break;
                       }else{
                           // If preferred property is "knows"... here I could assign what to say...
                           //but to assign "knows" as the preferred property is something... stupid?
                       }
                   }
              }else{ //The preferred property doesn't exist in the model...
                   // N.A. is asigned, but this should be check in "compile time"
              }
           }
       }
       //None of the provided preferences do fine...
       /**  May be we could search for any printable property...
       StmtIterator iter = indv.listProperties();
       Statement stmt = null;
       while (iter.hasNext()){
           stmt = iter.nextStatement();
           stmt.
       }  **/

       if (strValue == null){
           strValue = strValueNA;
       }
       return strValue;
    }

    /**
     * For each property in the model checks that its localname equals propName
     * @param m
     * @return  the first OntProperty found or null if the property is not found
     */
     private OntProperty getOntProperty(OntModel m){
        ExtendedIterator iter = m.listOntProperties();
        OntProperty op = null;
        boolean hasFoundProp = false;
        while (iter.hasNext()) {  //Search for a property with the same local name (namespace is not considered now)
            op = (OntProperty) iter.next();
            if (op.getLocalName().equals((propName))){
                hasFoundProp = true;
                break;
            }
        }
        return hasFoundProp? op : null;
    }

     /**
     * For each property in the model checks that its localname equals propName
     * @param m
     * @param propName
     * @return  the first OntProperty found or null if the property is not found
     */
     private OntProperty getOntPropertyWithPropertyName(OntModel m, String propName){
        ExtendedIterator iter = m.listOntProperties();
        OntProperty op = null;
        boolean hasFoundProp = false;
        while (iter.hasNext()) {  //Search for a property with the same local name (namespace is not considered now)
            op = (OntProperty) iter.next();
            if (op.getLocalName().equals(propName)){
                hasFoundProp = true;
                break;
            }
        }
        return hasFoundProp? op : null;
    }

    /**
     * Looks for the first Individual with a given OntProperty
     * @param m
     * @param op
     * @return The individual, or null in case of error.
     * @throws Exception In case of not existing property, blank node not convertible to Individual, or no subject
     *  with that property
     */
     private Individual getIndividualWithOntProperty(OntModel m, OntProperty op) throws Exception{
        Individual resIndv = null;
        if (op == null){
                throw new Exception("Property " + propName + " not found.");
        }else{ //property found
           ResIterator iterSubjs = m.listSubjectsWithProperty(op);
           Resource res = null;
           if (iterSubjs.hasNext()){
               res = iterSubjs.nextResource(); //Take the fist subject with that property
               if (!res.isAnon()){ //Is is a resource with a URI
                  resIndv = m.getIndividual(res.toString());
               }else{               //It is an individual with NO URI (a.k.a. blank node or annonymous node)
                  if (res.canAs (Individual.class)){
                     resIndv = (Individual)res.as(Individual.class);
                  }else{
                     throw new Exception("This blank node is not an individual: " + res.getId());
                  }
               }
           }else{
               throw new Exception(strValueNA);
           }
        }
        return resIndv;
    }
   /*
    private String[] getPreferredPropertyForRelation(String relationPropName){
        String[] prefsPerson = {"name", "givenname", "firstName", "family_name", "nick"};

        if (relationPropName.equals("knows")) return prefsPerson;
        return prefsPerson;
    }
    */

    public static void main(String[] args){

        String urlDefsFile = "file:///C:/Documents%20and%20Settings/Mariano%20Rico/Mis%20documentos/JSPWikiFiles2453/FOAF.20050603.rdf";
        //String urlInstancesFile = "file:///C:/Documents%20and%20Settings/Mariano%20Rico/Escritorio/mifoafupdated.rdf";
        //String urlInstancesFile = "http://www.ii.uam.es/~mrico/foaf.rdf";
        String urlInstancesFile = "http://www.w3.org/People/Berners-Lee/card";


        OntModelSpec modSpec = OntModelSpec.RDFS_MEM_RDFS_INF;  //This is critical... OWL or RDFS decides what an instance is
        OntModel m = ModelFactory.createOntologyModel(modSpec, null);
        //Reads Ontology defs
        Model desfsModel = FileManager.get().loadModel(urlDefsFile); // Is not necessary the ns or file format
        m.add(desfsModel);
        Model individualsModel = FileManager.get().loadModel(urlInstancesFile); // Is not necessary the ns or file format
        m.add(individualsModel);
        OntClass oc = m.getOntClass("http://xmlns.com/foaf/0.1/Person");
        ExtendedIterator iter = null;
        Individual defIdv = null;
        //Testing literal props
        {
            String [] literalProps = { "givenName",    //Property written wrong (it should be givenname)
                                       "givenname",
                                       "surname",      //property with no value
                                       "family_name",
                                       "name",
                                       "img",
                                       "depiction"};

            iter = m.listIndividuals(oc);
            while(iter.hasNext()){  //For each individual
                defIdv = (Individual) iter.next();
                for (int i = 0; i < literalProps.length; i++){
                    PropertyValueGetter pvg = new PropertyValueGetter(literalProps[i]);
                    try {
                       System.out.println("'" + literalProps[i] + "' has value(s) " + pvg.getPropertyValues(m, defIdv));
                    } catch(Exception e){
                       System.out.println(e.getMessage());
                    }
                }
            }
        }
        //Testing relation props
        String relationPropName = "knows";
        String[] preferred = {"firstName", "name"};
        {
            iter = m.listIndividuals(oc);
            PropertyValueGetter pvg = new PropertyValueGetter(relationPropName, preferred);
            while(iter.hasNext()){
               defIdv = (Individual) iter.next();
               try {
                  System.out.println("'" + relationPropName + "' is " + pvg.getPropertyValues(m, defIdv));

               } catch(Exception e){
                  System.out.println(e.getMessage());
               }
            }
         }
        //Testing hasValue
        String strPropWithNA = "surname";
        {
//            iter = m.listIndividuals(oc);
//            PropertyValueGetter pvg2 = new PropertyValueGetter(strPropWithNA);
//            while(iter.hasNext()){
//               defIdv = (Individual) iter.next();
//               try {
//                  System.out.println("has property " + strPropWithNA + " a value? :" + pvg2.hasPropertyValue(m, defIdv));
//               } catch(Exception e){
//                       System.out.println(e.getMessage());
//               }
//            }
        }
    }
}
